package com.generator.controller;

import com.RSA.RSAKeyPair;
import com.RSA.Safety;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.generator.entity.Permission;
import com.generator.entity.Reply;
import com.generator.mapper.PermissionMapper;
import com.util.IntoDB;
import com.util.MockService;
import com.util.Result;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.util.HashMap;
import java.util.List;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;

/**
 * @Author: htj
 * @Date: 2021/10/2 0002 上午 1:30
 * 处理请求转发模块
 */
@Slf4j
@RestController
@RequestMapping("/handleRequest")
@ResponseBody
public class HandleController {

    @Autowired
    private Safety safety;

    @Autowired
    private Result result;

    @Autowired
    private PermissionMapper permissionMapper;

    @Autowired
    private MockService mockService;

    @Autowired
    private IntoDB intoDB;

    //引用全局唯一的存储并发数的map
    @Autowired
    private HashMap<Integer, Semaphore> apiConcurrentMap;

    //引用全局唯一的存储超时时间的map
    @Autowired
    private HashMap<Integer, Integer> apiOvertimeMap;

    //引用全局唯一的存储开关的map
    @Autowired
    private HashMap<Integer, Integer> apiSwitchMap;

    //处理外部请求
    @GetMapping("/{api_id}/{thirdParty_id}/{content}")
    public Reply handleRequest(@PathVariable("api_id") Integer api_id,
                               @PathVariable("thirdParty_id") Integer thirdParty_id,
                               @PathVariable("content") String content) throws Exception {

        //加密，模拟外部请求以密文的形式进入网关
        System.out.println("初始请求报文：" + content);
        RSAKeyPair keyPair = safety.generateKeyPair();//生成本次请求的密钥对
        String ciphertext = safety.encryptByPublicKey(keyPair.getPublicKey(), content);//公钥加密后的密文
        System.out.println("公钥加密后：" + ciphertext);

        //首先判断接口号是否存在
        if (apiSwitchMap.containsKey(api_id) == false) {
            return result.getResult(4, 0, api_id, thirdParty_id, content);
        }

        //接口存在，判断开关
        if (apiSwitchMap.get(api_id) == 0) {//1代表接口可用，0代表不可用
            return result.getResult(1, 0, api_id, thirdParty_id, content);
        }

        //查询访问权限
        //为了提升网关性能，把这里的数据库访问变成map访问--------------------
        QueryWrapper queryWrapper = new QueryWrapper();
        queryWrapper.eq("api_id", api_id);
        queryWrapper.eq("thirdParty_id", thirdParty_id);
        List<Permission> permissionList = permissionMapper.selectList(queryWrapper);
        if (permissionList.size() == 0) {//没有权限访问接口
            return result.getResult(3, 0, api_id, thirdParty_id, content);
        }

        //限流
        int overtime = apiOvertimeMap.get(api_id);//从内存的map中获取该接口的超时时间。从数据库中查询太慢了，尤其是高并发且令牌数多时会更慢，严重影响网关性能
        boolean success = apiConcurrentMap.get(api_id).tryAcquire(overtime, TimeUnit.SECONDS);//根据接口id获取对应的concurrent。使用非阻塞tryAcquire实现超时控制，如果获取不到就快速返回失败
        if (success == false) {//没获取到许可证
            return result.getResult(2, 0, api_id, thirdParty_id, content);
        }

        //如果能走到这里，说明 接口打开，有权限，获取到许可证

        //在这里由网关来生成流水号
        //请求信息要先入库，然后再去调用服务，确保请求的时间比返回时间早
        int seqNo = intoDB.insertSend(api_id, thirdParty_id, content);//新的流水号

        //调用统一模拟服务并返回内容
        Reply reply = mockService.service(seqNo, api_id, thirdParty_id, ciphertext, keyPair);

        //返回信息入库
        intoDB.insertReturn(reply);

        //打日志
        log.info("流水号：{}    第三方id：{}    接口id：{}    请求内容：{}    返回状态：成功     返回内容：{}", Integer.toString(seqNo), Integer.toString(thirdParty_id), Integer.toString(api_id), content, reply.getContent());

        //一定要释放，否则导致接口假死无法处理请求
        apiConcurrentMap.get(api_id).release();

        //返回结果到前端
        return reply;
    }
}
